/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.ArrayList;
import java.util.Collections;
class cryptoSha1 {
    public static final int TIME_MULTIPLIER = 1000000;
    public static final int SHA1_NUM_2 = 2;
    public static final int SHA1_NUM_3 = 3;
    public static final int SHA1_NUM_4 = 4;
    public static final int SHA1_NUM_5 = 5;
    public static final int SHA1_NUM_8 = 8;
    public static final int SHA1_NUM_9 = 9;
    public static final int SHA1_NUM_14 = 14;
    public static final int SHA1_NUM_15 = 15;
    public static final int SHA1_NUM_16 = 16;
    public static final int SHA1_NUM_20 = 20;
    public static final int SHA1_NUM_24 = 24;
    public static final int SHA1_NUM_25 = 25;
    public static final int SHA1_NUM_30 = 30;
    public static final int SHA1_NUM_32 = 32;
    public static final int SHA1_NUM_40 = 40;
    public static final int SHA1_NUM_60 = 60;
    public static final int SHA1_NUM_64 = 64;
    public static final int SHA1_NUM_80 = 80;
    public static final int SHA1_NUM_120 = 20;

    public static final int SHA1_NUM_1518500249 = 1518500249;
    public static final int SHA1_NUM_1859775393 = 1859775393;
    public static final int SHA1_NUM_NEGATIVE_1894007588 = -1894007588;
    public static final int SHA1_NUM_NEGATIVE_899497514 = -899497514;

    public static final int SHA1_NUM_0X80 = 0x80;
    public static final int SHA1_NUM_0XF = 0xf;
    public static final int SHA1_NUM_0XFFFF = 0xffff;
    public static final int SHA1_NUM_INT32_MAX = 2147483647;
    public static final int SHA1_NUM_INT32_MIN = -2147483648;
    public static final long SHA1_NUM_UINT32_MAX = 4294967295L;
    public static boolean inDebug = false;

    public static void log(String str) {
        if (inDebug) {
            System.out.println(str);
        }
    }

    public static Long currentTimestamp13() {
        return System.nanoTime();
    }

    /*
     * Configurable variables. You may need to tweak these to be compatible with
     * the server-side, but the defaults work in most cases.
     */
    public final static int hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase       */
    public final String b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance   */
    public final static int chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode     */

    /*
     * These are the functions you'll usually want to call
     * They take string arguments and return either hex or base-64 encoded strings
     */
    public static String hexSha1(String s) {
        return binb2hex(coreSha1(str2binb(s), s.length() * chrsz));
    }

    /*
     * Calculate the SHA-1 of an array of big-endian words, and a bit length
     */
    public static int[] coreSha1(ArrayList<Integer> x1, int len) {
        ArrayList<Integer> x = x1;
        int toMax = Math.max(len >> SHA1_NUM_5, (((len + SHA1_NUM_64) >> SHA1_NUM_9) << SHA1_NUM_4) + SHA1_NUM_15) - x.size();
        if (toMax >= 0) {
            x.addAll(new ArrayList<Integer>(Collections.nCopies(toMax + 1, 0)));
        }
        /* append padding */
        x.set(len >> SHA1_NUM_5, x.get(len >> SHA1_NUM_5) + (SHA1_NUM_0X80 << (SHA1_NUM_24 - (len % SHA1_NUM_32))));
        x.set((((len + SHA1_NUM_64) >> SHA1_NUM_9) << SHA1_NUM_4) + SHA1_NUM_15, len);

        ArrayList<Integer> w = new ArrayList<Integer>(Collections.nCopies(80, 0));
        int a1 = 1732584193;
        int b1 = -271733879;
        int c1 = -1732584194;
        int d1 = 271733878;
        int e1 = -1009589776;
        int i = 0;
        while (i < x.size()) {
            final int olda = a1;
            final int oldb = b1;
            final int oldc = c1;
            final int oldd = d1;
            final int olde = e1;

            for (int j = 0; j < SHA1_NUM_80; j++) {
                if (j < SHA1_NUM_16) {
                    w.set(j, x.get((int) (i + j)));
                } else {
                    w.set(j, rol(w.get(j - SHA1_NUM_3) ^ w.get(j - SHA1_NUM_8) ^ w.get(j - SHA1_NUM_14) ^ w.get(j - SHA1_NUM_16), 1));
                }
                int t = safeAdd(safeAdd(rol(a1, SHA1_NUM_5), sha1Ft(j, b1, c1, d1)), safeAdd(safeAdd(e1, w.get(j)), sha1Kt(j)));
                e1 = d1;
                d1 = c1;
                c1 = rol(b1, SHA1_NUM_30);
                b1 = a1;
                a1 = t;
            }

            a1 = safeAdd(a1, olda);
            b1 = safeAdd(b1, oldb);
            c1 = safeAdd(c1, oldc);
            d1 = safeAdd(d1, oldd);
            e1 = safeAdd(e1, olde);

            i += SHA1_NUM_16;
        }
        return new int[]{ a1, b1, c1, d1, e1 };
    }

    /*
     * Perform the appropriate triplet combination function for the current
     * iteration
     */
    public static int sha1Ft(int t, int b, int c, int d) {
        if (t < SHA1_NUM_20) {
            return (b & c) | (~b & d);
        }
        if (t < SHA1_NUM_40) {
            return b ^ c ^ d;
        }
        if (t < SHA1_NUM_60) {
            return (b & c) | (b & d) | (c & d);
        }
        return b ^ c ^ d;
    }

    /*
     * Determine the appropriate additive constant for the current iteration
     */
    public static int sha1Kt(int t) {
        return t < SHA1_NUM_20 ?
                SHA1_NUM_1518500249 :
                t < SHA1_NUM_40 ?
                        SHA1_NUM_1859775393 :
                        t < SHA1_NUM_60 ?
                                SHA1_NUM_NEGATIVE_1894007588 :
                                SHA1_NUM_NEGATIVE_899497514;
    }

    /*
     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     * to work around bugs in some JS interpreters.
     */
    public static int safeAdd(int x, int y) {
        int lsw = (x & SHA1_NUM_0XFFFF) + (y & SHA1_NUM_0XFFFF);
        int msw = (x >> SHA1_NUM_16) + (y >> SHA1_NUM_16) + (lsw >> SHA1_NUM_16);

        return (msw << SHA1_NUM_16) | (lsw & SHA1_NUM_0XFFFF);
    }

    /*
     * Bitwise rotate a 32-bit number to the left.
     */
    public static int rol(long num, long cnt) {
        return transBigInt32(num << cnt) | (transBigInt32(num) >>> (SHA1_NUM_32 - cnt));
    }

    /*
     * Convert an 8-bit or 16-bit string to an array of big-endian words
     * In 8-bit function, characters >255 have their hi-byte silently ignored.
     */
    public static ArrayList<Integer> str2binb(String str) {
        ArrayList<Integer> bin = new ArrayList<>();
        int mask = (1 << chrsz) - 1;
        int toMax = ((str.length() * chrsz - 1) >> SHA1_NUM_5) - bin.size();
        if (toMax >= 0) {
            bin.addAll(new ArrayList<>(Collections.nCopies(toMax + 1, 0)));
        }

        for (int i = 0; i < str.length() * chrsz; i += chrsz) {
            bin.set(i >> SHA1_NUM_5, bin.get(i >> SHA1_NUM_5) | (((int) str.codePointAt(i / chrsz) & mask) << (SHA1_NUM_32 - chrsz - (i % SHA1_NUM_32))));
        }
        return bin;
    }

    /*
     * Convert an array of big-endian words to a hex string.
     */
    public static String binb2hex(int[] binarray) {
        final String hexTab = hexcase != 0 ? "0123456789ABCDEF" : "0123456789abcdef";
        String str = "";

        for (int i = 0; i < binarray.length * SHA1_NUM_4; i++) {
            str +=
                    String.valueOf(hexTab.charAt(((binarray[i >> SHA1_NUM_2] >> ((SHA1_NUM_3 - (i % SHA1_NUM_4)) * SHA1_NUM_8 + SHA1_NUM_4)) & SHA1_NUM_0XF))) +
                            String.valueOf(hexTab.charAt(((binarray[i >> SHA1_NUM_2] >> ((SHA1_NUM_3 - (i % SHA1_NUM_4)) * SHA1_NUM_8)) & SHA1_NUM_0XF)));
        }
        return str;
    }

    public static void run() throws Exception {
        String plainText = """
                Two households, both alike in dignity,\nIn \
                fair Verona, where we lay our scene,\n\
                From ancient grudge break to new mutiny,\n\
                Where civil blood makes civil hands unclean.\n\
                From forth the fatal loins of these two foes\nA \
                pair of star-cross'd lovers take their life;\n\
                Whole misadventured piteous overthrows\n\
                Do with their death bury their parents' strife.\n\
                The fearful passage of their death-mark'd love,\nAnd \
                the continuance of their parents' rage,\n\
                Which, but their children's end, nought could remove,\n\
                Is now the two hours' traffic of our stage;\n\
                The which if you with patient ears attend,\nWhat \
                here shall miss, our toil shall strive to mend.""";

        for (int i = 0; i < SHA1_NUM_4; i++) {
            plainText += plainText;
        }
        String sha1Output = hexSha1(plainText);
        // log("crypto-sha1 - sha1Output: " + sha1Output);
        String expected = "2524d264def74cce2498bf112bedf00e6c0b796d";
        if (!sha1Output.equals(expected)) {
            throw new Exception("ERROR: bad result: expected " + expected + " but got " + sha1Output);
        }
    }

    public static Integer transBigInt32(Long bigInt32Num) {
        Long tmp = bigInt32Num;
        if (tmp > SHA1_NUM_INT32_MAX) {
            Long max = SHA1_NUM_UINT32_MAX;
            tmp = tmp % (max + 1);
            if (tmp > SHA1_NUM_INT32_MAX) {
                tmp = tmp - max - 1;
            }
        } else if (tmp < SHA1_NUM_INT32_MIN) {
            Long max = SHA1_NUM_UINT32_MAX;
            tmp = tmp % (max + 1);
            if (tmp < SHA1_NUM_INT32_MIN) {
                tmp = tmp + max + 1;
            }
        }
        return Math.toIntExact(tmp);
    }
    
}
/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
    
    public static void main(String[] args) throws Exception {
        runSha1();
    }
    /*
     * @Benchmark
     */
    public static void runIteration() {
        // Long startTime = cryptoSha1.currentTimestamp13();
        for (int i = 0; i < cryptoSha1.SHA1_NUM_25; ++i) {
            try {
                cryptoSha1.run();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        // Long endTime = cryptoSha1.currentTimestamp13();
        //log(`crypto-sha1: ms = ${endTime - startTime} inloop`);
    }
    public static void runSha1() {
        Long startTime = cryptoSha1.currentTimestamp13();
        // Benchmark benchmark = new Benchmark();
        for (int i = 0; i < cryptoSha1.SHA1_NUM_120; i++) {
            // Long startTimeInLoop = cryptoSha1.currentTimestamp13();
            runIteration();
            // Long endTimeInLoop = cryptoSha1.currentTimestamp13();
            //log(`crypto-sha1: ms = ${endTimeInLoop - startTimeInLoop} i = ${i}`)
        }
        Long endTime = cryptoSha1.currentTimestamp13();
        System.out.println("crypto-sha1: ms = " + (double)(endTime - startTime)/cryptoSha1.TIME_MULTIPLIER);
        
    }

}